/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.interpreter;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dex.compiler.checker.type.MemberType;
import dex.compiler.checker.type.TypeManager;
import dex.compiler.interpreter.value.BooleanValue;
import dex.compiler.interpreter.value.MemberValue;
import dex.compiler.interpreter.value.NullValue;
import dex.compiler.interpreter.value.Value;
import dex.compiler.model.base.Identifiers;
import dex.compiler.model.definition.Callable;
import dex.compiler.model.definition.ClassDef;
import dex.compiler.model.definition.TypedName;
import dex.compiler.model.definition.Unit;
import dex.compiler.model.program.NameSearcher;
import dex.compiler.model.program.Path;
import dex.compiler.model.program.Program;
import dex.compiler.model.program.Programs;
import dex.compiler.problem.AbortReporter;
import dex.misc.DefaultScope;
import dex.misc.Scope;


/**
 * State for the interpreter.
 */
final class State {


	/**
	 * Describes an entry in a function call stack.
	 * Contains all the information needed to execute a function.
	 */
	private static class FunctionInfo {
		
		/** The path the function being executed. */
		Path path;
		
		/** The local variables for the function. */
		Scope<String,Value> locals;
		
		/** The stack of temporary values for the function. */
		List<Value> stack;
		
		
		/**
		 * Constructs a new <code>FunctionInfo</code>.
		 * 
		 * @param path  the path to the function
		 * @param parameters  the parameters for the function
		 */
		public FunctionInfo(Path path, List<Value> parameters) {
			this.path = path;
			this.locals = new DefaultScope<String,Value>();
			this.stack = new ArrayList<Value>();
			// FIXME: Add "this" if class method
			Callable f = (Callable)path.getDefinition();
			for (int i = 0; i < parameters.size(); i++) {
				TypedName t = f.getParameters().get(i);
				locals.mostRecent().put(t.getName(), parameters.get(i));
			}
		}
		
	}
	

	/**
	 * The program to interpret.
	 */
	final private Program program;


	/**
	 * Manager for the program's types.
	 */
	final private TypeManager manager;


	/**
	 * Global variables.
	 */
	final private Map<String,Value> globals;


	/**
	 * Function call stack.
	 */
	final private List<FunctionInfo> functions;


	/**
	 * Marked function call indexes.  Used to unwind the
	 * function call stack when an exception is caught.
	 */
	final private List<Integer> marks;

	/**
	 * Constructor.
	 * 
	 * @param program  the program to interpret
	 */
	public State(Program program) {
		if (program == null) {
			throw new IllegalArgumentException("Program may not be null.");
		}
		this.globals = new HashMap<String,Value>();
		InterpretedProgram p = new InterpretedProgram(program, globals);
		this.manager = new TypeManager(p, AbortReporter.INSTANCE);
		p.setTypeManager(manager);
		this.program = p;
		this.functions = new ArrayList<FunctionInfo>();
		this.marks = new ArrayList<Integer>();
	}


	/**
	 * Returns the info for the currently executing function.
	 * 
	 * @return  info for the currently executing function
	 */
	private FunctionInfo mostRecent() {
		return functions.get(functions.size() - 1);
	}

	
	/**
	 * Returns the local variable with the given name.
	 * 
	 * @param name   the name to check
	 * @return   
	 */
	public Value getLocal(String name) {
		for (Map<String,Value> scope: mostRecent().locals.layers()) {
			Value r = scope.get(name);
			if (r != null) {
				return r;
			}
		}
		return null;
	}

	
	/**
	 * Sets a local variable.
	 * 
	 * @param name    the name of the local variable
	 * @param value   the new value for that variable
	 */
	public void setLocal(String name, Value value) {
		mostRecent().locals.mostRecent().put(name, value);
	}

	
	public boolean localExists(String name) {
		for (Map<String,Value> scope: mostRecent().locals.layers()) {
			if (scope.containsKey(name)) {
				return true;
			}
		}
		return false;
	}
	
	public void addLocalScope() {
		mostRecent().locals.add();
	}
	
	
	public void removeLocalScope() {
		mostRecent().locals.remove();
	}


	/**
	 * Sets a global variable.
	 * 
	 * @param name    the name of the global variable to set
	 * @param value   the value for that global variable
	 */
	public void setGlobal(String name, Value value) {
		if (!Identifiers.isQualified(name)) {
			throw new IllegalArgumentException("Global name must be qualified.");
		}
		globals.put(name, value);
	}


	/**
	 * Returns a global variable.
	 * 
	 * @param name  the name of the global variable to return
	 * @return  the value of that global variable
	 */
	public Value getGlobal(String name) {
		return globals.get(name);
	}


	/**
	 * Throws a ball with the given name.
	 * 
	 * @param ballName  the name of the ball to throw
	 */
	public void pitch(String ballName) {
		// FIXME: Pre-cache runtime errors
		NameSearcher searcher = new NameSearcher(ClassDef.class, ballName);
		Path path = Programs.search(program, context(), searcher).get(0);		
		MemberType mt = manager.getMemberType(path);
		throw new Ball(new MemberValue(manager, mt));
	}
	
	
	/**
	 * Pushes a value onto the expression stack.
	 * 
	 * @param value  the value to push
	 */
	public void push(Value value) {
		mostRecent().stack.add(value);
	}


	/**
	 * Returns the topmost element of the expression stack.
	 * 
	 * @return  the topmost element of the expression stack
	 */
	public Value peek() {
		List<Value> stack = mostRecent().stack;
		return stack.get(stack.size() - 1);
	}


	/**
	 * Pops a value from the expression stack.
	 * 
	 * @return  the popped element
	 */
	public Value pop() {
		List<Value> stack = mostRecent().stack;
		return stack.remove(stack.size() - 1);
	}


	/**
	 * Pops a non-null value from the expression stack.
	 * If the popped value is NullValue.INSTANCE, then this method
	 * will raise Ball("dex:core:Null").
	 * 
	 * @return  the non-null popped value
	 */
	public Value popReference() {
		Value r = pop();
		if (r == NullValue.INSTANCE) {
			pitch("dex:core:Null");
		}
		return r;
	}

	
	public boolean popBoolean() {
		BooleanValue r = (BooleanValue)pop();
		return r.value();
	}
	
	public void checkBounds(Value[] array, long index) {
		int i = (int)index;
		if ((i < 0) || (i >= array.length)) {
			pitch("dex:core:Bounds");
		}
	}

	/**
	 * Pops a list of values from the expression stack.
	 * 
	 * @param size  the number of values to pop
	 * @return  the popped value
	 */
	public List<Value> pop(int size) {
		Value[] result = new Value[size];
		for (int i = size - 1; i >= 0; i--) {
			result[i] = pop();
		}
		return Arrays.asList(result);
	}


	/**
	 * Returns the context of the currently executing function.
	 * 
	 * @return  the unit who owns the current executing function
	 */
	public Unit context() {
		return mostRecent().path.getUnit();
	}


	/**
	 * Prepares to execute a function.
	 * 
	 * @param function    the function to execute
	 * @param parameters  the parameters for that function
	 */
	public void enter(Path function, List<Value> parameters) {
		functions.add(new FunctionInfo(function, parameters));
	}


	/**
	 * Cleans up function resources after executing a function.
	 */
	public void exit() {
		functions.remove(functions.size() - 1);
	}
	
	
	/**
	 * Returns the program being interpreted.
	 * 
	 * @return  the program being interpreted
	 */
	public Program getProgram() {
		return program;
	}


	/**
	 * Returns the manager of the program's types.
	 * 
	 * @return  the type manager
	 */
	public TypeManager getTypeManager() {
		return manager;
	}


	/**
	 * Marks the current state of the function call stack.
	 */
	public void mark() {
		marks.add(functions.size());
	}


	/**
	 * Unwinds the function call stack to the most recently marked state.
	 */
	public void reset() {
		int i = marks.remove(marks.size() - 1);
		functions.subList(i, functions.size()).clear();
	}
}
